#pragma once

#include <vector>
#include <memory>
#include <thread>
#include <future>
#include <stdexcept>
#include <functional>

#include "mpmc_queue.h"

class ThreadPool {
  using TaskType = std::function<void()>;

 public:
  explicit ThreadPool(size_t threads_n = std::thread::hardware_concurrency() / 2);
  ~ThreadPool();

  template <typename F, typename... Args>
  decltype(auto) Enqueue(F &&f, Args &&... args) {
    using ReturnType = std::invoke_result_t<F, Args...>;

    auto task = std::make_shared<std::packaged_task<ReturnType()>>(
        [f = std::forward<F>(f), tup = std::make_tuple(std::forward<Args>(
                                     args)...)]() -> decltype(auto) {
          return std::apply(f, tup);
        });

    std::future<ReturnType> ret = task->get_future();
    queue_.Enqueue([task]() { (*task)(); });
    return ret;
  }

 private:
  std::vector<std::thread> workers_;  // 工作线程
  MPMCQueue<TaskType> queue_;         // 任务队列
};
